#include "EnemyManager.h"
#include "HomeManager.h"
#include "SlimEnemy.h"
#include "KingSlimEnemy.h"
#include "SkeletonEnemy.h"
#include "GoblinEnemy.h"
#include "GoblinPriestEnemy.h"
#include "ConfigManager.h"
#include "CoinManager.h"
#include "BulletManager.h"

void EnemyManager::OnUpdate(double delta)
{
    for (Enemy *enemy: enemyList) {
        enemy->OnUpdate(delta);
    }

    ProcessHomeCollision();
    ProcessBulletCollision();
    RemoveInvalidEnemy();
}

void EnemyManager::OnRender(SDL_Renderer *renderer)
{
    for (Enemy *enemy: enemyList) {
        enemy->OnRender(renderer);
    }
}

void EnemyManager::SpawnEnemy(EnemyType type, int idxSpawnPoint)
{
    static Vector2 position;
    static SDL_Rect &rectTileMap = ConfigManager::Instance().rectTileMap;
    static const Map::SpawnerRouterPool &spawnerRouterPool =
            ConfigManager::Instance().map.GetSpawnerRouterPool();

    if (!spawnerRouterPool.count(idxSpawnPoint)) { return; }
    Enemy *enemy = nullptr;
    switch (type) {
        case EnemyType::Slim:
            enemy = new SlimEnemy();
            break;
        case EnemyType::KingSlim:
            enemy = new KingSlimEnemy();
            break;
        case EnemyType::Skeleton:
            enemy = new SkeletonEnemy();
            break;
        case EnemyType::Goblin:
            enemy = new GoblinEnemy();
            break;
        case EnemyType::GoblinPriest:
            enemy = new GoblinPriestEnemy();
            break;
        default:
            enemy = new SlimEnemy();
            break;
    }

    enemy->SetOnSkillReleased([&](Enemy *enemySrc) {
        double recoverRadius = enemySrc->GetRecoverRadius();
        if (recoverRadius < 0) return;

        const Vector2 posSrc = enemySrc->GetPosition();
        for (Enemy *enemy_dst: enemyList) {
            const Vector2 &posDst = enemy_dst->GetPosition();
            double distance = (posDst - posSrc).Length();
            if (distance <= recoverRadius) {
                enemy_dst->IncreaseHp(enemySrc->GetRecoverIntensity());
            }
        }
    });

    const Router::IdxList &idxList = spawnerRouterPool.at(idxSpawnPoint).GetIdxList();
    position.x = rectTileMap.x + idxList[0].x * SIZE_TILE + SIZE_TILE / 2;
    position.y = rectTileMap.y + idxList[0].y * SIZE_TILE + SIZE_TILE / 2;

    enemy->SetPosition(position);
    enemy->SetRouter(&spawnerRouterPool.at(idxSpawnPoint));
    enemyList.push_back(enemy);
}

bool EnemyManager::CheckCleared()
{
    return enemyList.empty();
}

std::vector<Enemy *> &EnemyManager::GetEnemyList()
{
    return enemyList;
}

void EnemyManager::ProcessHomeCollision()
{
    static const SDL_Point &idxHome = ConfigManager::Instance().map.GetIdxHome();
    static const SDL_Rect &rectTileMap = ConfigManager::Instance().rectTileMap;
    static const Vector2 positionHomeTile = {
            (double) rectTileMap.x + idxHome.x * SIZE_TILE,
            (double) rectTileMap.y + idxHome.y * SIZE_TILE
    };
    for (Enemy *enemy: enemyList) {
        if (enemy->CanRemove()) {
            continue;
        }
        const Vector2 &position = enemy->GetPosition();
        if (position.x >= positionHomeTile.x
            && position.y >= positionHomeTile.y
            && position.x <= positionHomeTile.x + SIZE_TILE
            && position.y <= positionHomeTile.y + SIZE_TILE) {
            enemy->MakeInvalid();
            HomeManager::Instance().DecreaseHp(enemy->GetDamage());
        }
    }
}

void EnemyManager::ProcessBulletCollision()
{
    static const std::vector<Bullet *> &bulletList = BulletManager::Instance().GetBulletList();

    for (Enemy *enemy: enemyList) {
        if (enemy->CanRemove()) continue;
        const Vector2 &sizeEnemy = enemy->GetSize();
        const Vector2 &posEnemy = enemy->GetPosition();
        for (Bullet *bullet: bulletList) {
            if (!bullet->CanCollide()) continue;
            const Vector2 &posBullet = bullet->GetPosition();
            if (posBullet.x >= posEnemy.x - sizeEnemy.x / 2
                && posBullet.y >= posEnemy.y - sizeEnemy.y / 2
                && posBullet.x <= posEnemy.x + sizeEnemy.x / 2
                && posBullet.y <= posEnemy.y + sizeEnemy.y / 2) {
                double damage = bullet->GetDamage();
                double damage_range = bullet->GetDamageRange();
                if (damage_range < 0) {
                    enemy->DecreaseHp(damage);
                    if (enemy->CanRemove())
                        TrySpawnCoinProp(posEnemy, enemy->GetRewardRatio());
                } else {
                    for (Enemy *targetEnemy: enemyList) {
                        const Vector2 &posTargetEnemy = targetEnemy->GetPosition();
                        if ((posTargetEnemy - posBullet).Length() <= damage_range) {
                            targetEnemy->DecreaseHp(damage);
                            if (targetEnemy->CanRemove())
                                TrySpawnCoinProp(posEnemy, targetEnemy->GetRewardRatio());
                        }
                    }
                }
                bullet->OnCollide(enemy);
            }
        }
    }
}

void EnemyManager::RemoveInvalidEnemy()
{
    enemyList.erase(std::remove_if(enemyList.begin(), enemyList.end(),
                                   [](const Enemy *enemy) {
                                       bool deletable = enemy->CanRemove();
                                       if (deletable) delete enemy;
                                       return deletable;
                                   }), enemyList.end());
}

void EnemyManager::TrySpawnCoinProp(const Vector2 &position, double ratio)
{
    static CoinManager &instance = CoinManager::Instance();

    if ((double) (rand() % 100) / 100 <= ratio)
        instance.SpawnCoinProp(position);
}